Skip to content

ldaps: add LDAPS support - #264

Open
shridhargadekar wants to merge 8 commits into
SSSD:masterfrom
shridhargadekar:cert_ldaps
Open

shridhargadekar wants to merge 8 commits into
SSSD:masterfrom
shridhargadekar:cert_ldaps

Conversation

@shridhargadekar

Copy link
Copy Markdown
Contributor
  • Add export_root_ca_certificate() to ADHost, SambaHost, and IPAHost
  • Add CertUtils (client.cert) utility for system-level CA cert install into /etc/openldap/ldap.conf — works with adcli, realmd, ldapsearch
  • Add SSSDCommonConfiguration helpers: ad_use_ldaps(), samba_use_ldaps(), ipa_set_tls_cacert() for SSSD-specific LDAPS configuration

Comment thread sssd_test_framework/hosts/ad.py Outdated
Comment thread sssd_test_framework/hosts/ipa.py Outdated
Comment thread sssd_test_framework/hosts/samba.py Outdated
Comment thread sssd_test_framework/utils/cert.py Outdated
Comment thread sssd_test_framework/utils/cert.py Outdated
Comment thread sssd_test_framework/utils/cert.py Outdated
Comment thread sssd_test_framework/utils/sssd.py Outdated
@shridhargadekar
shridhargadekar force-pushed the cert_ldaps branch 4 times, most recently from a94e0cc to 41ed743 Compare August 10, 2026 19:05

@spoore1 spoore1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a couple of quick notes until I can test this out.

Comment thread sssd_test_framework/roles/ad.py
Comment thread sssd_test_framework/roles/ad.py
@shridhargadekar

Copy link
Copy Markdown
Contributor Author

Depends upon #255

Comment thread sssd_test_framework/roles/client.py Outdated
Comment thread sssd_test_framework/utils/sssd.py Outdated
Comment thread sssd_test_framework/roles/client.py Outdated
Comment thread sssd_test_framework/roles/ad.py
Comment thread sssd_test_framework/roles/client.py Outdated
Comment thread sssd_test_framework/utils/sssd.py Outdated

@spoore1 spoore1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure the AD export method is working as expected. My testing showed the certutil fail and the old commit worked better but, I'm not sure it's pulling the expected certificate.

Comment thread sssd_test_framework/roles/ad.py
Comment thread sssd_test_framework/roles/ad.py
Comment thread sssd_test_framework/roles/ad.py Outdated
Comment thread sssd_test_framework/roles/client.py Outdated
@shridhargadekar
shridhargadekar force-pushed the cert_ldaps branch 3 times, most recently from effb2c3 to b9de358 Compare August 27, 2026 19:59
Comment thread sssd_test_framework/roles/client.py Outdated
Comment thread sssd_test_framework/roles/client.py Outdated
@sumit-bose

Copy link
Copy Markdown
Contributor

Hi,

it looks like OpenSSLUtils(client, client.fs).install_ca_cert() is only called for the IPA and Samba topologies but not for AD. Is this expected?

Do I see it correctly that with the given approach topology_controller.py in the adcli tests directory has to be updated as well to run LDAPS tests correctly?

bye,
Sumit

@shridhargadekar

Copy link
Copy Markdown
Contributor Author

Hi,

it looks like OpenSSLUtils(client, client.fs).install_ca_cert() is only called for the IPA and Samba topologies but not for AD. Is this expected?

AD had to be intentionally skipped as there is limitation in this approach of using topology controller. The cert export for AD is in the ADCertificateAuthority.get_ca_cert() and is running powershell against DC certificate store. however this method is on AD role. AFAIK Topology_controller receives host objects, whereas role objects created per test via pytest fixtures, thus provider.ca.get_ca_cert() is not callable from topology_setup()

The bare minimal fix will be to move get_ca_cert() and _get_ca_config from ADCertificateAuthority to ADHost directly. this will make it accessible from the topology controller. This can be done if required. Moving rest of ADCertificateAuthority methods like sign(), request(), revoke() doesn't make any sense. So code, logic is getting duplicated.

For now, AD tests can still install the cert per-test via client.ssl.install_ca_cert_from_server(provider.host.hostname).

Do I see it correctly that with the given approach topology_controller.py in the adcli tests directory has to be updated as well to run LDAPS tests correctly?

Yes, these changes are additional cost/complexity for any project like adcli, realmd, etc with their own topology_controllers.

bye, Sumit

The more I evaluate this topology controller against role-bases approach, the role-base is making more sense as it's self-contained ssl_tls(), easy to follow, does not require to modify any other project. the role based approach can further simplified with GenericProvider, I'll revisit this after more evaluation.

danlavu
danlavu previously approved these changes Oct 2, 2026

@danlavu danlavu left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

| This is so much better, thank you! Approved, but please rebase.

I didn't see your comment above, so I'm reverting the approval. This should work, looking.

@danlavu
danlavu self-requested a review October 2, 2026 12:42
@danlavu
danlavu dismissed their stale review October 2, 2026 12:43

I missed your comment, and the method is wrong.

@danlavu

danlavu commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Something like this will work.

diff --git a/sssd_test_framework/topology_controllers.py b/sssd_test_framework/topology_controllers.py
index b0503ad..08c226f 100644
--- a/sssd_test_framework/topology_controllers.py
+++ b/sssd_test_framework/topology_controllers.py
@@ -16,6 +16,7 @@ from .hosts.keycloak import KeycloakHost
 from .hosts.ldap import LDAPHost
 from .hosts.samba import SambaHost
 from .misc.ssh import retry_command
+from .roles.ad import ADCertificateAuthority
 from .utils.tools import OpenSSLUtils
 
 __all__ = [
@@ -290,13 +291,20 @@ class ADTopologyController(ProvisionedBackupTopologyController):
                 provider.fs.backup("/etc/resolv.conf")
                 provider.fs.write("/etc/resolv.conf", f"search {provider.domain}\nnameserver 127.0.0.1\n\n")
 
-        # Install Samba CA certificate so LDAPS tests can use it without per-test setup.
-        # Done before the provisioned check so it runs even on already-provisioned containers.
+        # Install the provider's CA certificate on the client so LDAPS tests can use it
+        # without per-test setup. Done before the provisioned check so it runs even on
+        # already-provisioned containers.
         if isinstance(provider, SambaHost):
             ca_cert_path = provider.config.get("ca_cert_path", "/var/data/certs/ca.crt")
             result = provider.conn.run(f"cat {ca_cert_path}", raise_on_error=False)
             if result.rc == 0 and result.stdout.strip():
                 OpenSSLUtils(client, client.fs).install_ca_cert(result.stdout)
+        elif isinstance(provider, ADHost):
+            try:
+                cert_pem = ADCertificateAuthority(provider).export_root_ca_certificate()
+                OpenSSLUtils(client, client.fs).install_ca_cert(cert_pem)
+            except RuntimeError as e:
+                self.logger.warning(f"Unable to install AD CA certificate on {client.hostname}: {e}")
 
         if self.provisioned:
             self.logger.info(f"Topology '{self.name}' is already provisioned")

@danlavu

danlavu commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Here is another solution, which you'd need to move some methods back into hosts/ad, so roles/ad can inherit the methods. It doens't make sense on a naming convention, needs to be this way for inheritance. I am fine with either solution.

diff --git a/sssd_test_framework/hosts/ad.py b/sssd_test_framework/hosts/ad.py
index d603795..26babcb 100644
--- a/sssd_test_framework/hosts/ad.py
+++ b/sssd_test_framework/hosts/ad.py
@@ -2,6 +2,7 @@
 
 from __future__ import annotations
 
+import textwrap
 from pathlib import PureWindowsPath
 from typing import Any
 
@@ -100,6 +101,74 @@ class ADHost(BaseDomainHost):
 
         return self.__naming_context
 
+    def get_ca_config(self) -> str:
+        """
+        Get the AD Certificate Services CA configuration string
+        (``hostname\\CA-name``) used by ``certreq``/``certutil -config``.
+
+        Falls back to a guessed ``hostname\\domain-CA`` value if the CA
+        configuration cannot be determined (e.g. AD CS is not installed).
+
+        :return: CA configuration string.
+        :rtype: str
+        """
+        result = self.conn.run("certutil -dump", raise_on_error=False)
+        if result.rc == 0:
+            for line in result.stdout_lines:
+                if "Config:" in line:
+                    return line.split(":", 1)[1].strip()
+
+        return f"{self.hostname}\\{self.domain}-CA"
+
+    def get_ca_cert(self) -> str:
+        """
+        Get the root CA certificate in PEM format from the Windows
+        certificate store.
+
+        Looks up the self-signed root CA certificate matching the AD CS
+        CA name in ``Cert:\\LocalMachine\\Root``. If AD CS is not installed
+        (or the root certificate is not found there), falls back to the
+        certificate in ``Cert:\\LocalMachine\\My`` matching the same name,
+        which covers self-signed DC certificates used for LDAPS without AD CS.
+
+        :return: Root CA certificate in PEM format.
+        :rtype: str
+        :raises RuntimeError: If the CA certificate cannot be retrieved.
+        """
+        ca_name = self.get_ca_config().split("\\", 1)[1].strip('"')
+        result = self.conn.run(
+            textwrap.dedent(f"""\
+                $ca = Get-ChildItem -Path Cert:\\LocalMachine\\Root | Where-Object {{
+                    $_.Subject -like '*CN={ca_name}*' -and $_.Issuer -eq $_.Subject
+                }} | Select-Object -First 1
+                if ($ca) {{
+                    [System.Convert]::ToBase64String($ca.Export('Cert'))
+                }} else {{
+                    $ca = Get-ChildItem -Path Cert:\\LocalMachine\\My | Where-Object {{
+                        $_.Subject -like '*CN={ca_name}*'
+                    }} | Select-Object -First 1
+                    if ($ca) {{
+                        [System.Convert]::ToBase64String($ca.Export('Cert'))
+                    }} else {{
+                        Write-Error "CA certificate not found"
+                        exit 1
+                    }}
+                }}
+            """),
+            raise_on_error=False,
+        )
+
+        if result.rc != 0:
+            raise RuntimeError(f"Failed to get CA certificate: {result.stderr}!")
+
+        ca_cert_b64 = result.stdout.strip()
+
+        if not ca_cert_b64:
+            raise RuntimeError("CA certificate not found in certificate stores!")
+
+        ca_cert_lines = [ca_cert_b64[i : i + 64] for i in range(0, len(ca_cert_b64), 64)]
+        return "-----BEGIN CERTIFICATE-----\n" + "\n".join(ca_cert_lines) + "\n-----END CERTIFICATE-----\n"
+
     def disconnect(self) -> None:
         return
 
diff --git a/sssd_test_framework/roles/ad.py b/sssd_test_framework/roles/ad.py
index e583a07..a10d06b 100644
--- a/sssd_test_framework/roles/ad.py
+++ b/sssd_test_framework/roles/ad.py
@@ -2691,7 +2691,7 @@ class ADCertificateAuthority(GenericCertificateAuthority):
 
         try:
             result = self.host.conn.run(
-                f'certreq -submit -config "{self._get_ca_config()}" "{req_path}" "{cert_path}"',
+                f'certreq -submit -config "{self.host.get_ca_config()}" "{req_path}" "{cert_path}"',
                 raise_on_error=False,
                 timeout=30,
             )
@@ -2771,7 +2771,7 @@ class ADCertificateAuthority(GenericCertificateAuthority):
 
         self.host.conn.run(f'certreq -q -new "{inf_path}" "{req_path}"')
 
-        self.host.conn.run(f'certreq -submit -config "{self._get_ca_config()}" "{req_path}" "{cert_path}"')
+        self.host.conn.run(f'certreq -submit -config "{self.host.get_ca_config()}" "{req_path}" "{cert_path}"')
 
         self.export_pfx(cert_path, pfx_path, password=password)
 
@@ -2841,7 +2841,7 @@ class ADCertificateAuthority(GenericCertificateAuthority):
 
         self.host.conn.run(f'certreq -q -sign -cert "{enrollment_agent_hash}" "{req_path}" "{signed_req_path}"')
 
-        self.host.conn.run(f'certreq -submit -config "{self._get_ca_config()}" "{signed_req_path}" "{cert_path}"')
+        self.host.conn.run(f'certreq -submit -config "{self.host.get_ca_config()}" "{signed_req_path}" "{cert_path}"')
 
         self.export_pfx(cert_path, pfx_path)
 
@@ -2905,7 +2905,7 @@ class ADCertificateAuthority(GenericCertificateAuthority):
         serial = self._get_cert_serial(cert_path)
         reason_code = self._revocation_reason_to_code(reason)
 
-        self.host.conn.run(f'certutil -config "{self._get_ca_config()}" -revoke {serial} {reason_code}')
+        self.host.conn.run(f'certutil -config "{self.host.get_ca_config()}" -revoke {serial} {reason_code}')
 
     def revoke_hold(self, cert_path: str) -> None:
         """
@@ -2929,7 +2929,7 @@ class ADCertificateAuthority(GenericCertificateAuthority):
         """
         serial = self._get_cert_serial(cert_path)
 
-        self.host.conn.run(f'certutil -config "{self._get_ca_config()}" -revoke {serial} 8')  # 8 = removeFromCRL
+        self.host.conn.run(f'certutil -config "{self.host.get_ca_config()}" -revoke {serial} 8')  # 8 = removeFromCRL
 
     def get(self, cert_path: str) -> dict[str, list[str]]:
         """
@@ -2996,21 +2996,6 @@ class ADCertificateAuthority(GenericCertificateAuthority):
 
         return attrs_ad_parse(result.stdout)
 
-    def _get_ca_config(self) -> str:
-        """
-        Get CA configuration string.
-
-        :return: CA configuration string.
-        :rtype: str
-        """
-        result = self.host.conn.run("certutil -dump", raise_on_error=False)
-        if result.rc == 0:
-            for line in result.stdout_lines:
-                if "Config:" in line:
-                    return line.split(":", 1)[1].strip()
-
-        return f"{self.host.hostname}\\{self.host.domain}-CA"
-
     def _get_cert_serial(self, cert_path: str) -> str:
         """
         Extract certificate serial number.
@@ -3086,43 +3071,15 @@ class ADCertificateAuthority(GenericCertificateAuthority):
         """
         Get the CA certificate in PEM format.
 
+        Delegates to :meth:`ADHost.get_ca_cert` so the same logic is
+        available to callers that only have access to the host object
+        (e.g. :class:`~sssd_test_framework.topology_controllers.ADTopologyController`).
+
         :return: CA certificate in PEM format.
         :rtype: str
         :raises RuntimeError: If CA certificate cannot be retrieved.
         """
-        ca_name = self._get_ca_config().split("\\", 1)[1].strip('"')
-        result = self.host.conn.run(
-            textwrap.dedent(f"""\
-                $ca = Get-ChildItem -Path Cert:\\LocalMachine\\Root | Where-Object {{
-                    $_.Subject -like '*CN={ca_name}*' -and $_.Issuer -eq $_.Subject
-                }} | Select-Object -First 1
-                if ($ca) {{
-                    [System.Convert]::ToBase64String($ca.Export('Cert'))
-                }} else {{
-                    $ca = Get-ChildItem -Path Cert:\\LocalMachine\\My | Where-Object {{
-                        $_.Subject -like '*CN={ca_name}*'
-                    }} | Select-Object -First 1
-                    if ($ca) {{
-                        [System.Convert]::ToBase64String($ca.Export('Cert'))
-                    }} else {{
-                        Write-Error "CA certificate not found"
-                        exit 1
-                    }}
-                }}
-            """),
-            raise_on_error=False,
-        )
-
-        if result.rc != 0:
-            raise RuntimeError(f"Failed to get CA certificate: {result.stderr}!")
-
-        ca_cert_b64 = result.stdout.strip()
-
-        if not ca_cert_b64:
-            raise RuntimeError("CA certificate not found in certificate stores!")
-
-        ca_cert_lines = [ca_cert_b64[i : i + 64] for i in range(0, len(ca_cert_b64), 64)]
-        return "-----BEGIN CERTIFICATE-----\n" + "\n".join(ca_cert_lines) + "\n-----END CERTIFICATE-----\n"
+        return self.host.get_ca_cert()
 
     def export_root_ca_certificate(self) -> str:
         """
diff --git a/sssd_test_framework/topology_controllers.py b/sssd_test_framework/topology_controllers.py
index b0503ad..68ce5c6 100644
--- a/sssd_test_framework/topology_controllers.py
+++ b/sssd_test_framework/topology_controllers.py
@@ -290,13 +290,19 @@ class ADTopologyController(ProvisionedBackupTopologyController):
                 provider.fs.backup("/etc/resolv.conf")
                 provider.fs.write("/etc/resolv.conf", f"search {provider.domain}\nnameserver 127.0.0.1\n\n")
 
-        # Install Samba CA certificate so LDAPS tests can use it without per-test setup.
-        # Done before the provisioned check so it runs even on already-provisioned containers.
+        # Install the provider's CA certificate on the client so LDAPS tests can use it
+        # without per-test setup. Done before the provisioned check so it runs even on
+        # already-provisioned containers.
         if isinstance(provider, SambaHost):
             ca_cert_path = provider.config.get("ca_cert_path", "/var/data/certs/ca.crt")
             result = provider.conn.run(f"cat {ca_cert_path}", raise_on_error=False)
             if result.rc == 0 and result.stdout.strip():
                 OpenSSLUtils(client, client.fs).install_ca_cert(result.stdout)
+        elif isinstance(provider, ADHost):
+            try:
+                OpenSSLUtils(client, client.fs).install_ca_cert(provider.get_ca_cert())
+            except RuntimeError as e:
+                self.logger.warning(f"Unable to install AD CA certificate on {client.hostname}: {e}")
 
         if self.provisioned:
             self.logger.info(f"Topology '{self.name}' is already provisioned")

# Install IPA CA certificate so LDAPS/STARTTLS tests can use it without per-test setup.
# Done before the provisioned check so it runs even on already-provisioned containers.
cert_pem = ipa.fs.read("/etc/ipa/ca.crt")
OpenSSLUtils(client, client.fs).install_ca_cert(cert_pem)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a problem, that there is no check for existing certificates and the cleanup code occurs on topology teardown, which will re-add the certificate to the store on for every test run. Topology teardown only occur when typologies change. We should add a check before issuing install_ca_cert, we can do it here or in install_ca_cert. Up to you.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated a check here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the check? It read the CA certificate on the IPA server before it installs it? We want to check the client so it doesn't have to retrieve the file again.

Comment thread sssd_test_framework/utils/tools.py Outdated

return self.install_ca_cert(certs[-1], name=name, cert_path=cert_path)

def _configure_tls_cacert(self) -> None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should really be called _configure_openldap they're other certificate stores we may configure.

]
lines.append("SASL_CBINDING tls-endpoint")

self.fs.write(ldap_conf, "\n".join(lines) + "\n")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add fs.backup($PATH) so it resets on teardown.

)
self.fs.chown(homedir, user=user, group=group, args=["-R"])

return self.fs.read(f"{homedir}/.ssh/{file}.pub"), self.fs.read(f"{homedir}/.ssh/{file}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a typo.

# Install IPA CA certificate so LDAPS/STARTTLS tests can use it without per-test setup.
# Done before the provisioned check so it runs even on already-provisioned containers.
cert_pem = ipa.fs.read("/etc/ipa/ca.crt")
OpenSSLUtils(client, client.fs).install_ca_cert(cert_pem)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the check? It read the CA certificate on the IPA server before it installs it? We want to check the client so it doesn't have to retrieve the file again.

# Install CA certificate so LDAPS tests can use it without per-test setup.
# Done before the provisioned check so it runs even on already-provisioned containers.
if isinstance(provider, SambaHost):
ca_cert_path = provider.config.get("ca_cert_path", "/var/data/certs/ca.crt")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also needs a check.

  • Is the CA certificate installed on the client, no, get certificate, yes, does the certificate match the provider?

I suggest naming the certificate to match the topology when you initially get them or hash/checksum

Comment thread sssd_test_framework/hosts/ad.py Outdated
:rtype: str
:raises RuntimeError: If CA certificate cannot be retrieved.
"""
ca_name = self.get_ca_config().split("\\", 1)[1].strip('"')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can simplify this. Since we're already using certutil.

certutil.exe -f -"ca.cert" C:\Windows\Temp\ca.crt
certutil.exe -f -encode C:\Windows\Temp\ca.crt C:\Windows\Temp\ca.pem
Get-Content C:\Windows\Temp\ca.pem -Raw
PS C:\Users\vagrant> certutil.exe -f -"ca.cert" C:\Windows\Temp\ca.crt
CA cert[0]: 3 -- Valid
CA cert[0]:
-----BEGIN CERTIFICATE-----
MIIDVzCCAj+gAwIBAgIQFqpWzzgyZ5ZFdTEfrioBYDANBgkqhkiG9w0BAQsFADA+
MRQwEgYKCZImiZPyLGQBGRYEdGVzdDESMBAGCgmSJomT8ixkARkWAmFkMRIwEAYD
VQQDEwlhZC1Sb290Q0EwHhcNMjYwOTIxMTcxNTM4WhcNMzYwOTIxMTcyNTM3WjA+
MRQwEgYKCZImiZPyLGQBGRYEdGVzdDESMBAGCgmSJomT8ixkARkWAmFkMRIwEAYD
VQQDEwlhZC1Sb290Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDR
4Tc8etmO+MhnCPvKjuJdNYcJ1Aqo+6ZlNsNH7ueknLkMqRCf4sV4fNj+rQY7CFjF
/ATBaD3yX+cgw6wMuQODmYY2PLkQa7jdNkFmRnF5B7OyiGwznA94uD9LD3ve9nyJ
/kyuY5AWW7D07mExY+5jgmfCh+S5i0OTY0Tn9mRddWpMjvQFn098GIgzhnV1dshW
VtcmKYXWpagaKVy1qa46PEKhIRKfZVUgrl45VxotElE8KFG1GN39IWyT4jTHqCFh
4+VDIUOwz1HQ/MdFR+m48OazQdI5GtFqDbQ5H+600L7Ie1qXuyraMYK3t6xa6OHv
A451Lr+5RXd0X3+MtoEFAgMBAAGjUTBPMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8E
BTADAQH/MB0GA1UdDgQWBBSpki2czbECFIosuBBTNZdDSqZ/mDAQBgkrBgEEAYI3
FQEEAwIBADANBgkqhkiG9w0BAQsFAAOCAQEADJhXzQVToYEZDvAKu5vmodKQW4oB
eWGaT9AKPHqJ9KgWmfWXaNmm/eNQidfKkuegYYfFTtUuZC9NaTTcAjSzSs+C8eiu
e9reh3NZRTDaY/64NZovyvZUL0XturB7SQGr79RGzekDu4r6zzD810zmJw4EkdgA
w8z4mckcFxZDHgcY8oD6jhCHOaYWs7knClIZ0euu+DGExeQ+OAIA/cfgUxLMNqjo
nF0f/m69M3iqk7JrKQXJJgAZfRmoV1nuCftMF73IlGkmUjT2yHsnn8nJdXDZDP1f
SVUgAH5ckb1sNrlTqpnjFkCL1CrtqRNNlwDLq+mT+Vfz3VCCjS7oOvFgkw==
-----END CERTIFICATE-----

CertUtil: -ca.cert command completed successfully.

PS C:\Users\vagrant> certutil.exe -f -encode C:\Windows\Temp\ca.crt C:\Windows\Temp\ca.pem
Input Length = 859
Output Length = 1240
CertUtil: -encode command completed successfully.

PS C:\Users\vagrant> Get-Content C:\Windows\Temp\ca.pem -Raw
-----BEGIN CERTIFICATE-----
MIIDVzCCAj+gAwIBAgIQFqpWzzgyZ5ZFdTEfrioBYDANBgkqhkiG9w0BAQsFADA+
MRQwEgYKCZImiZPyLGQBGRYEdGVzdDESMBAGCgmSJomT8ixkARkWAmFkMRIwEAYD
VQQDEwlhZC1Sb290Q0EwHhcNMjYwOTIxMTcxNTM4WhcNMzYwOTIxMTcyNTM3WjA+
MRQwEgYKCZImiZPyLGQBGRYEdGVzdDESMBAGCgmSJomT8ixkARkWAmFkMRIwEAYD
VQQDEwlhZC1Sb290Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDR
4Tc8etmO+MhnCPvKjuJdNYcJ1Aqo+6ZlNsNH7ueknLkMqRCf4sV4fNj+rQY7CFjF
/ATBaD3yX+cgw6wMuQODmYY2PLkQa7jdNkFmRnF5B7OyiGwznA94uD9LD3ve9nyJ
/kyuY5AWW7D07mExY+5jgmfCh+S5i0OTY0Tn9mRddWpMjvQFn098GIgzhnV1dshW
VtcmKYXWpagaKVy1qa46PEKhIRKfZVUgrl45VxotElE8KFG1GN39IWyT4jTHqCFh
4+VDIUOwz1HQ/MdFR+m48OazQdI5GtFqDbQ5H+600L7Ie1qXuyraMYK3t6xa6OHv
A451Lr+5RXd0X3+MtoEFAgMBAAGjUTBPMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8E
BTADAQH/MB0GA1UdDgQWBBSpki2czbECFIosuBBTNZdDSqZ/mDAQBgkrBgEEAYI3
FQEEAwIBADANBgkqhkiG9w0BAQsFAAOCAQEADJhXzQVToYEZDvAKu5vmodKQW4oB
eWGaT9AKPHqJ9KgWmfWXaNmm/eNQidfKkuegYYfFTtUuZC9NaTTcAjSzSs+C8eiu
e9reh3NZRTDaY/64NZovyvZUL0XturB7SQGr79RGzekDu4r6zzD810zmJw4EkdgA
w8z4mckcFxZDHgcY8oD6jhCHOaYWs7knClIZ0euu+DGExeQ+OAIA/cfgUxLMNqjo
nF0f/m69M3iqk7JrKQXJJgAZfRmoV1nuCftMF73IlGkmUjT2yHsnn8nJdXDZDP1f
SVUgAH5ckb1sNrlTqpnjFkCL1CrtqRNNlwDLq+mT+Vfz3VCCjS7oOvFgkw==
-----END CERTIFICATE-----

.. code-block:: python
:caption: Example usage

@pytest.mark.topology(KnownTopologyGroup.AnyDC)

@danlavu danlavu Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use actual tests for examples, because they'll be generated sphinx docs.

.. code-block:: python
:caption: Example usage

@pytest.mark.topology(KnownTopologyGroup.AnyDC)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a real example, please.

@shridhargadekar
shridhargadekar force-pushed the cert_ldaps branch 2 times, most recently from c31b96c to e2a23db Compare October 5, 2026 16:57
@spoore1

spoore1 commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

FYI, latest updates are passing:

======================================== short test summary info ========================================
PASSED tests/test_adcli.py::test_adcli__join_and_update_with_ldaps (ad)
PASSED tests/test_adcli.py::test_adcli__delete_computer_with_ldaps (ad)
PASSED tests/test_adcli.py::test_adcli__join_and_update_with_ldaps (samba)
PASSED tests/test_adcli.py::test_adcli__delete_computer_with_ldaps (samba)
=================================== 4 passed, 84 deselected in 59.91s ===================================

@spoore1 spoore1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small change suggestion unless I'm misreading something.

Comment thread sssd_test_framework/roles/ad.py
@shridhargadekar
shridhargadekar force-pushed the cert_ldaps branch 2 times, most recently from 060ea99 to a279b3d Compare October 6, 2026 13:41
@sumit-bose

Copy link
Copy Markdown
Contributor

Hi,

I'm still fine with this PR, but I think ti would be good to squash some of the related patches into one.

bye,
Sumit

shridhargadekar and others added 6 commits October 7, 2026 01:48
Add export_root_ca_certificate() abstract method to GenericProvider
and implement it in the AD, IPA, and Samba roles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add OpenSSLUtils to utils/tools.py with install_ca_cert(),
install_ca_cert_from_server(), and _configure_openldap() helpers.
Add install_ca_cert() and ssl attribute to the Client role.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ADTopologyController (Samba) and IPATopologyController now install the
provider's CA certificate on the client via OpenSSLUtils before the
provisioned check, so LDAPS/STARTTLS tests have the cert available
without any per-test setup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace use_ldaps() with ssl_tls() in SSSDCommonConfiguration. The CA
certificate is now pre-installed by the topology controller, so ssl_tls()
only sets the SSSD domain options (ad_use_ldaps or ldap_id_use_start_tls)
and ldap_tls_cacert path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove install_ca_cert(), install_ca_cert_from_server(), use_ldaps(), and
_configure_tls_cacert() from the Client role. Add self.ssl (OpenSSLUtils)
for tests that still need to install a cert manually (e.g. AD with no AD CS).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move cert export logic from ADCertificateAuthority to ADHost so topology
controllers can access it directly without going through role fixtures.

ADCertificateAuthority._get_ca_config() and get_ca_cert() now delegate
to self.host, keeping the role-level API unchanged.

ADTopologyController gains an ADHost cert install branch with two-level
fallback: ADCS/PowerShell first, then openssl s_client, so cert install
works regardless of whether Windows Certificate Services is present.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Skip the write and update-ca-trust call if the certificate file already
exists with identical content. This avoids redundant re-installation on
every topology setup cycle when using provisioned containers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@spoore1 spoore1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just two minor questions about using the cat command.

Code appears to be working and I ran it with a slight modification to the tests in SSSD/sssd#9137

Comment thread sssd_test_framework/utils/tools.py Outdated
ldap_conf = "/etc/openldap/ldap.conf"

self.fs.backup(ldap_conf)
result = self.host.conn.run(f"cat {ldap_conf}", raise_on_error=False)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason here for running the cat command instead of using self.fs.read()?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated to self.fs.read()

pls check now

if isinstance(provider, SambaHost):
if not client.fs.exists("/etc/pki/ca-trust/source/anchors/samba-ca.crt"):
ca_cert_path = provider.config.get("ca_cert_path", "/var/data/certs/ca.crt")
result = provider.conn.run(f"cat {ca_cert_path}", raise_on_error=False)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason here for cat instead of self.fs.read()?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated to self.fs.read()

- topology_controllers: check client before fetching cert from provider
  to skip redundant SSH round-trips on re-provisioned containers; name
  certs by topology (ipa-ca.crt, samba-ca.crt, ad-ca.crt); replace
  conn.run cat with fs.exists/fs.read
- utils/tools: add fs.backup() for /etc/openldap/ldap.conf so it is
  restored on teardown; replace conn.run cat with fs.exists/fs.read
- utils/sssd: ssl_tls() derives default cacert path from provider type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants